/*

   pam_encfs by Anders Aagaard <aagaande@gmail.com>

   ############################################################################
   #    Copyright (C) 2008 by Anders Aagaard                                          
   #    aagaande@gmail.com                                             
   #                                                                          
   #    This program is free software; you can redistribute it and#or modify  
   #    it under the terms of the GNU General Public License as published by  
   #    the Free Software Foundation; either version 2 of the License, or     
   #    (at your option) any later version.                                   
   #                                                                          
   #    This program is distributed in the hope that it will be useful,       
   #    but WITHOUT ANY WARRANTY; without even the implied warranty of        
   #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         
   #    GNU General Public License for more details.                          
   #                                                                          
   #    You should have received a copy of the GNU General Public License     
   #    along with this program; if not, write to the                         
   #    Free Software Foundation, Inc.,                                       
   #    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             
   ############################################################################ */

/*
 * This program automatically sets PERFORMANCE/ONDEMAND/POWERSAVE cpufreq options.
 * It defaults to ONDEMAND
 * Sets to POWERSAVE if coretemp (cpu specific) temperature is too high.
 * Sets to PERFORMANCE on all cpu's if load on any single cpu goes over 80%.
 * Note that because of timer interrupts this isn't 100% accurate calculating cpu usage.
 */


#define LOOP_DELAY 5 // How often to check in seconds.
#define MAXTEMP 73 // coretemp degrees which is too high (per cpu), will lower to powersave for that specific cpu.

// IDLE_MIN is the minimum cpu usage before increasing to PERFORMANCE.
// This knows about SMP, so if you got 32 cpu's, and any one of them go over 80% (by default) it will switch to PERFORMANCE.
#define IDLE_MIN 80
//#define DEBUG 1 // Show debug info, can be handy when testing.



#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


#define GOV_PERFORMANCE "performance"
#define GOV_ONDEMAND "ondemand"
#define GOV_POWERSAVE "powersave"

typedef struct CPUInfo_t {
	// user, nice, system, idle, iowait
	unsigned long long u_old, n_old, s_old, i_old;
	unsigned long long u, n, s, i;
	float user, nice, system, idle;
} CPUInfo_t;

typedef struct CPUGOV_t {
	// user, nice, system, idle, iowait
	char *governator;
	char *current_governator;
	int can_ondemand; // Dont switch to ondemand until we wanted to twice, /proc/stat isn't 100% reliable thanks to interrupts.
} CPUGOV_t;

static int cpus = 0;
static CPUGOV_t *governators;
static CPUInfo_t cpuinfo;

#define READ_INTO_BUFFER(file,buffer,len) \
	int f = open(file,O_RDONLY); \
	assert(f > 0); \
	int r = read(f,buffer,len); \
	assert(r > 0); \
	close(f); 
	
#ifdef DEBUG
char *getGovernator(int cpu)
{
	char path[60];
	char buffer[30];
	
	sprintf(path,"/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor",cpu);
	
	READ_INTO_BUFFER(path,buffer,20);
	
	if (strncmp(buffer,GOV_ONDEMAND,8) == 0)
		return GOV_ONDEMAND;
	if (strncmp(buffer,GOV_PERFORMANCE,11) == 0)
		return GOV_PERFORMANCE;
	return 0;
}
#endif

void updateCpu()
{
	FILE *fp = NULL;
	int r;
	
	static unsigned long long u, n, s, i;
	float scale;
	
	// Scan /proc/stat, get overall cpu data from first data.
	fp = fopen("/proc/stat", "r");
	assert(fp != NULL);
	char buf[320];
	memset(&buf,0,320);
	
	fgets(buf, sizeof(buf), fp);
	// Move cpudata_current to cpudata_old
	cpuinfo.u_old = cpuinfo.u;
	cpuinfo.n_old = cpuinfo.n;
	cpuinfo.s_old = cpuinfo.s;
	cpuinfo.i_old = cpuinfo.i;
	// Scan new data into cpudata_current
	r = sscanf(buf, "cpu %Lu %Lu %Lu %Lu",&cpuinfo.u,&cpuinfo.n,&cpuinfo.s,&cpuinfo.i);
	assert(cpuinfo.u > 0);		
	fclose(fp);
	
	// Now calculate how much cpu usage that means:
	u = cpuinfo.u - cpuinfo.u_old;
	n = cpuinfo.n - cpuinfo.n_old;
	s = cpuinfo.s - cpuinfo.s_old;
	i = cpuinfo.i - cpuinfo.i_old;

	scale = 100.0 / (float)(u + n + s + i);
	cpuinfo.user = (float)u * scale;
	cpuinfo.nice = (float)n * scale;
	cpuinfo.system = (float)s * scale;
	cpuinfo.idle = (float)i * scale;
}

void countCpus()
{
	FILE *fp = NULL;
	char buf[320];
	cpus = -1;
	int tmpi;
	
	fp = fopen("/proc/stat", "r");
	while (fgets(buf, sizeof(buf), fp) && sscanf(buf, "cpu %d",&tmpi)) {
		cpus ++;
	}
	fclose(fp);
	#ifdef DEBUG
	printf("Found %d cpu's\n",cpus);
	#endif
}


void updateGovernators()
{
	int f,r;
	char path[60];
	int cpu;
	
	for (cpu = 0; cpu < cpus; cpu++) {
		#ifdef DEBUG
		if (governators[cpu].governator != governators[cpu].current_governator)
			printf("CPU%d %s* ",cpu,governators[cpu].governator);
		else
			printf("CPU%d %s ",cpu,governators[cpu].governator);
		#endif

		if (governators[cpu].governator != governators[cpu].current_governator) {
			// Check if we want to and cant set GOV_ONDEMAND.
			if (governators[cpu].governator == GOV_ONDEMAND && (!governators[cpu].can_ondemand)) {
				#ifdef DEBUG
				printf("Waiting with setting ondemand\n");
				#endif
				// Can't set it to ondemand yet, but we can next try:
				governators[cpu].can_ondemand = 1;
				governators[cpu].governator = governators[cpu].current_governator;
				continue;
			}

			sprintf(path,"/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor",cpu);
			f = open(path,O_WRONLY);
			assert(f > 0);
			r = write(f,governators[cpu].governator,strlen(governators[cpu].governator));
			assert(r > 0);
			close(f);
			governators[cpu].current_governator = governators[cpu].governator;
		}
		governators[cpu].can_ondemand = 0;
		
	}
	#ifdef DEBUG
	printf("\n");
	#endif
}

int checkTemp(int cpu)
{
	char path[50];
	char buf[20];
	sprintf(path,"/sys/devices/platform/coretemp.%d/temp1_input",cpu);
	
	READ_INTO_BUFFER(path,&buf,20);
	return (atoi(buf) / 1000);
}

int main(void)
{
	int cpu;
	printf("cpuGovernator 0.1 by aagaande@gmail.com\n");
	
	countCpus();
	// And the governators:
	governators = malloc(sizeof(governators) + (sizeof(CPUGOV_t) * (cpus + 1)));
	memset(governators,0,sizeof(governators) + (sizeof(CPUGOV_t) * (cpus + 1)));
			
	for(cpu = 0; cpu < cpus; cpu++) {
		governators[cpu].governator = GOV_ONDEMAND;
		governators[cpu].can_ondemand = 1;
	}
	updateGovernators();
	
	#ifdef DEBUG
	assert(strcmp(getGovernator(0),GOV_ONDEMAND) == 0);
	#endif


	updateCpu();
	while (1) {
		sleep(LOOP_DELAY);
		#ifdef DEBUG
		printf("\n");
		#endif
		updateCpu();
		

		// Default to ondemand
		for(cpu = 0; cpu < cpus; cpu++) {
			governators[cpu].governator = GOV_ONDEMAND;
		}

		// Check performance
		if (((float)100.0 - cpuinfo.idle) * 4 > IDLE_MIN) {
			// Cpu usage high, lets set cpus to performance.
			for(cpu = 0; cpu < cpus; cpu++) {
				governators[cpu].governator = GOV_PERFORMANCE;
			}
		}
		
		// Temperature check
		for (cpu = 0; cpu < cpus; cpu++) {
			#ifdef DEBUG
			if (cpu == 0)
				printf("LOAD %.2f%%  ",(float)100.0 - cpuinfo.idle);
			printf("CPU%d %dc",cpu,checkTemp(cpu));
			if (cpu == cpus - 1)
				printf("\n");
			else
				printf(" - ");
			#endif
			if (checkTemp(cpu) > MAXTEMP) {
				governators[cpu].governator = GOV_POWERSAVE;
			}
		}
		
		updateGovernators();
	}
	
	free(governators);
}
